﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using l1cs.server.model.Instance;
using l1cs.server.datatables;
using l1cs.server.templates;
using l1cs.server.serverpackets;
using l1cs.server.model.skill;

namespace l1cs.server.model
{
    public class L1WeaponSkill
    {
        private static Logger _log = Logger.getLogger( typeof( L1WeaponSkill ).FullName );

        private static Random _random = new Random();

        private int _weaponId;

        private int _probability;

        private int _fixDamage;

        private int _randomDamage;

        private int _area;

        private int _skillId;

        private int _skillTime;

        private int _effectId;

        private int _effectTarget; // エフェクトの対象 0:相手 1:自分

        private bool _isArrowType;

        private int _attr;

        public L1WeaponSkill( int weaponId, int probability, int fixDamage,
                int randomDamage, int area, int skillId, int skillTime,
                int effectId, int effectTarget, bool isArrowType, int attr )
        {
            _weaponId = weaponId;
            _probability = probability;
            _fixDamage = fixDamage;
            _randomDamage = randomDamage;
            _area = area;
            _skillId = skillId;
            _skillTime = skillTime;
            _effectId = effectId;
            _effectTarget = effectTarget;
            _isArrowType = isArrowType;
            _attr = attr;
        }

        public int getWeaponId()
        {
            return _weaponId;
        }

        public int getProbability()
        {
            return _probability;
        }

        public int getFixDamage()
        {
            return _fixDamage;
        }

        public int getRandomDamage()
        {
            return _randomDamage;
        }

        public int getArea()
        {
            return _area;
        }

        public int getSkillId()
        {
            return _skillId;
        }

        public int getSkillTime()
        {
            return _skillTime;
        }

        public int getEffectId()
        {
            return _effectId;
        }

        public int getEffectTarget()
        {
            return _effectTarget;
        }

        public bool isArrowType()
        {
            return _isArrowType;
        }

        public int getAttr()
        {
            return _attr;
        }

        public static double getWeaponSkillDamage( L1PcInstance pc, L1Character cha, int weaponId )
        {
            L1WeaponSkill weaponSkill = WeaponSkillTable.getInstance().getTemplate( weaponId );
            if ( pc == null || cha == null || weaponSkill == null )
            {
                return 0;
            }

            int chance = _random.Next( 100 ) + 1;
            if ( weaponSkill.getProbability() < chance )
            {
                return 0;
            }

            int skillId = weaponSkill.getSkillId();
            if ( skillId != 0 )
            {
                L1Skills skill = SkillsTable.getInstance().getTemplate( skillId );
                if ( skill != null && skill.getTarget().Equals( "buff" ) )
                {
                    if ( !isFreeze( cha ) )
                    { // 凍結状態orカウンターマジック中
                        cha.setSkillEffect( skillId, weaponSkill.getSkillTime() * 1000 );
                    }
                }
            }

            int effectId = weaponSkill.getEffectId();
            if ( effectId != 0 )
            {
                int chaId = 0;
                if ( weaponSkill.getEffectTarget() == 0 )
                {
                    chaId = cha.getId();
                }
                else
                {
                    chaId = pc.getId();
                }
                bool isArrowType = weaponSkill.isArrowType();
                if ( !isArrowType )
                {
                    pc.sendPackets( new S_SkillSound( chaId, effectId ) );
                    pc.broadcastPacket( new S_SkillSound( chaId, effectId ) );
                }
                else
                {
                    S_UseAttackSkill packet = new S_UseAttackSkill( pc, cha.getId(),
                            effectId, cha.getX(), cha.getY(), ActionCodes
                            .ACTION_Attack, false );
                    pc.sendPackets( packet );
                    pc.broadcastPacket( packet );
                }
            }

            double damage = 0;
            int randomDamage = weaponSkill.getRandomDamage();
            if ( randomDamage != 0 )
            {
                damage = _random.Next( randomDamage );
            }
            damage += weaponSkill.getFixDamage();

            int area = weaponSkill.getArea();
            if ( area > 0 || area == -1 )
            { // 範囲の場合
                foreach ( L1Object l1object in L1World.getInstance().getVisibleObjects( cha, area ) )
                {
                    if ( l1object == null )
                    {
                        continue;
                    }
                    if ( !( l1object is L1Character ) )
                    {
                        continue;
                    }
                    if ( l1object.getId() == pc.getId() )
                    {
                        continue;
                    }
                    if ( l1object.getId() == cha.getId() )
                    { // 攻撃対象はL1Attackで処理するため除外
                        continue;
                    }

                    // 攻撃対象がMOBの場合は、範囲内のMOBにのみ当たる
                    // 攻撃対象がPC,Summon,Petの場合は、範囲内のPC,Summon,Pet,MOBに当たる
                    if ( cha is L1MonsterInstance )
                    {
                        if ( !( l1object is L1MonsterInstance ) )
                        {
                            continue;
                        }
                    }
                    if ( cha is L1PcInstance
                            || cha is L1SummonInstance
                            || cha is L1PetInstance )
                    {
                        if ( !( l1object is L1PcInstance
                                || l1object is L1SummonInstance
                                || l1object is L1PetInstance
                                || l1object is L1MonsterInstance ) )
                        {
                            continue;
                        }
                    }

                    damage = calcDamageReduction( pc, (L1Character)l1object, damage,
                            weaponSkill.getAttr() );
                    if ( damage <= 0 )
                    {
                        continue;
                    }
                    if ( l1object is L1PcInstance )
                    {
                        L1PcInstance targetPc = (L1PcInstance)l1object;
                        targetPc.sendPackets( new S_DoActionGFX( targetPc.getId(), ActionCodes.ACTION_Damage ) );
                        targetPc.broadcastPacket( new S_DoActionGFX( targetPc.getId(), ActionCodes.ACTION_Damage ) );
                        targetPc.receiveDamage( pc, (int)damage, false );
                    }
                    else if ( l1object is L1SummonInstance
                          || l1object is L1PetInstance
                          || l1object is L1MonsterInstance )
                    {
                        L1NpcInstance targetNpc = (L1NpcInstance)l1object;
                        targetNpc.broadcastPacket( new S_DoActionGFX( targetNpc
                                .getId(), ActionCodes.ACTION_Damage ) );
                        targetNpc.receiveDamage( pc, (int)damage );
                    }
                }
            }

            return calcDamageReduction( pc, cha, damage, weaponSkill.getAttr() );
        }

        public static double getBaphometStaffDamage( L1PcInstance pc,
                L1Character cha )
        {
            double dmg = 0;
            int chance = _random.Next( 100 ) + 1;
            if ( 14 >= chance )
            {
                int locx = cha.getX();
                int locy = cha.getY();
                int sp = pc.getSp();
                int intel = pc.getInt();
                double bsk = 0;
                if ( pc.hasSkillEffect( L1SkillId.BERSERKERS ) )
                {
                    bsk = 0.2;
                }
                dmg = ( intel + sp ) * ( 1.8 + bsk ) + _random.Next( intel + sp )
                        * 1.8;
                S_EffectLocation packet = new S_EffectLocation( locx, locy, 129 );
                pc.sendPackets( packet );
                pc.broadcastPacket( packet );
            }
            return calcDamageReduction( pc, cha, dmg, L1Skills.ATTR_EARTH );
        }

        public static double getDiceDaggerDamage( L1PcInstance pc, L1PcInstance targetPc, L1ItemInstance weapon )
        {
            double dmg = 0;
            int chance = _random.Next( 100 ) + 1;
            if ( 3 >= chance )
            {
                dmg = targetPc.getCurrentHp() * 2 / 3;
                if ( targetPc.getCurrentHp() - dmg < 0 )
                {
                    dmg = 0;
                }
                String msg = weapon.getLogName();
                pc.sendPackets( new S_ServerMessage( 158, msg ) );
                // \f1%0が蒸発してなくなりました。
                pc.getInventory().removeItem( weapon, 1 );
            }
            return dmg;
        }

        public static double getKiringkuDamage( L1PcInstance pc, L1Character cha )
        {
            int dmg = 0;
            int dice = 5;
            int diceCount = 2;
            int value = 0;
            int kiringkuDamage = 0;
            int charaIntelligence = 0;
            //int getTargetMr = 0;
            if ( pc.getWeapon().getItem().getItemId() == 270 )
            {
                value = 16;
            }
            else
            {
                value = 14;
            }

            for ( int i = 0; i < diceCount; i++ )
            {
                kiringkuDamage += ( _random.Next( dice ) + 1 );
            }
            kiringkuDamage += value;

            int spByItem = pc.getSp() - pc.getTrueSp(); // アイテムによるSP変動
            charaIntelligence = pc.getInt() + spByItem - 12;
            if ( charaIntelligence < 1 )
            {
                charaIntelligence = 1;
            }
            double kiringkuCoefficientA = ( 1.0 + charaIntelligence * 3.0 / 32.0 );

            kiringkuDamage *= (int)kiringkuCoefficientA;

            double kiringkuFloor = Math.Floor( (double)kiringkuDamage );

            dmg += (int)( kiringkuFloor + pc.getWeapon().getEnchantLevel() + pc.getOriginalMagicDamage() );

            if ( pc.hasSkillEffect( L1SkillId.ILLUSION_AVATAR ) )
            {
                dmg += 10;
            }

            if ( pc.getWeapon().getItem().getItemId() == 270 )
            {
                pc.sendPackets( new S_SkillSound( pc.getId(), 6983 ) );
                pc.broadcastPacket( new S_SkillSound( pc.getId(), 6983 ) );
            }
            else
            {
                pc.sendPackets( new S_SkillSound( pc.getId(), 7049 ) );
                pc.broadcastPacket( new S_SkillSound( pc.getId(), 7049 ) );
            }

            return calcDamageReduction( pc, cha, dmg, 0 );
        }

        public static double getAreaSkillWeaponDamage( L1PcInstance pc, L1Character cha, int weaponId )
        {
            double dmg = 0;
            int probability = 0;
            int attr = 0;
            int chance = _random.Next( 100 ) + 1;
            if ( weaponId == 263 )
            { // フリージングランサー
                probability = 5;
                attr = L1Skills.ATTR_WATER;
            }
            else if ( weaponId == 260 )
            { // レイジングウィンド
                probability = 4;
                attr = L1Skills.ATTR_WIND;
            }
            if ( probability >= chance )
            {
                int sp = pc.getSp();
                int intel = pc.getInt();
                int area = 0;
                int effectTargetId = 0;
                int effectId = 0;
                L1Character areaBase = cha;
                double damageRate = 0;

                if ( weaponId == 263 )
                { // フリージングランサー
                    area = 3;
                    damageRate = 1.4D;
                    effectTargetId = cha.getId();
                    effectId = 1804;
                    areaBase = cha;
                }
                else if ( weaponId == 260 )
                { // レイジングウィンド
                    area = 4;
                    damageRate = 1.5D;
                    effectTargetId = pc.getId();
                    effectId = 758;
                    areaBase = pc;
                }
                double bsk = 0;
                if ( pc.hasSkillEffect( L1SkillId.BERSERKERS ) )
                {
                    bsk = 0.2;
                }
                dmg = ( intel + sp ) * ( damageRate + bsk ) + _random.Next( intel + sp ) * damageRate;
                pc.sendPackets( new S_SkillSound( effectTargetId, effectId ) );
                pc.broadcastPacket( new S_SkillSound( effectTargetId, effectId ) );

                foreach ( L1Object l1object in L1World.getInstance().getVisibleObjects( areaBase, area ) )
                {
                    if ( l1object == null )
                    {
                        continue;
                    }
                    if ( !( l1object is L1Character ) )
                    {
                        continue;
                    }
                    if ( l1object.getId() == pc.getId() )
                    {
                        continue;
                    }
                    if ( l1object.getId() == cha.getId() )
                    { // 攻撃対象は除外
                        continue;
                    }

                    // 攻撃対象がMOBの場合は、範囲内のMOBにのみ当たる
                    // 攻撃対象がPC,Summon,Petの場合は、範囲内のPC,Summon,Pet,MOBに当たる
                    if ( cha is L1MonsterInstance )
                    {
                        if ( !( l1object is L1MonsterInstance ) )
                        {
                            continue;
                        }
                    }
                    if ( cha is L1PcInstance
                            || cha is L1SummonInstance
                            || cha is L1PetInstance )
                    {
                        if ( !( l1object is L1PcInstance
                                || l1object is L1SummonInstance
                                || l1object is L1PetInstance
                                || l1object is L1MonsterInstance ) )
                        {
                            continue;
                        }
                    }

                    dmg = calcDamageReduction( pc, (L1Character)l1object, dmg, attr );
                    if ( dmg <= 0 )
                    {
                        continue;
                    }
                    if ( l1object is L1PcInstance )
                    {
                        L1PcInstance targetPc = (L1PcInstance)l1object;
                        targetPc.sendPackets( new S_DoActionGFX( targetPc.getId(),
                                ActionCodes.ACTION_Damage ) );
                        targetPc.broadcastPacket( new S_DoActionGFX( targetPc.getId(),
                                 ActionCodes.ACTION_Damage ) );
                        targetPc.receiveDamage( pc, (int)dmg, false );
                    }
                    else if ( l1object is L1SummonInstance
                          || l1object is L1PetInstance
                          || l1object is L1MonsterInstance )
                    {
                        L1NpcInstance targetNpc = (L1NpcInstance)l1object;
                        targetNpc.broadcastPacket( new S_DoActionGFX( targetNpc.getId(), ActionCodes.ACTION_Damage ) );
                        targetNpc.receiveDamage( pc, (int)dmg );
                    }
                }
            }
            return calcDamageReduction( pc, cha, dmg, attr );
        }

        public static double getLightningEdgeDamage( L1PcInstance pc, L1Character cha )
        {
            double dmg = 0;
            int chance = _random.Next( 100 ) + 1;
            if ( 4 >= chance )
            {
                int sp = pc.getSp();
                int intel = pc.getInt();
                double bsk = 0;
                if ( pc.hasSkillEffect( L1SkillId.BERSERKERS ) )
                {
                    bsk = 0.2;
                }
                dmg = ( intel + sp ) * ( 2 + bsk ) + _random.Next( intel + sp ) * 2;

                pc.sendPackets( new S_SkillSound( cha.getId(), 10 ) );
                pc.broadcastPacket( new S_SkillSound( cha.getId(), 10 ) );
            }
            return calcDamageReduction( pc, cha, dmg, L1Skills.ATTR_WIND );
        }

        public static void giveArkMageDiseaseEffect( L1PcInstance pc, L1Character cha )
        {
            int chance = _random.Next( 1000 ) + 1;
            int probability = ( 5 - ( ( cha.getMr() / 10 ) * 5 ) ) * 10;
            if ( probability == 0 )
            {
                probability = 10;
            }
            if ( probability >= chance )
            {
                L1SkillUse l1skilluse = new L1SkillUse();
                l1skilluse.handleCommands( pc, 56, cha.getId(), cha.getX(), cha.getY(), null, 0, L1SkillUse.TYPE_GMBUFF );
            }
        }

        public static void giveFettersEffect( L1PcInstance pc, L1Character cha )
        {
            int fettersTime = 8000;
            if ( isFreeze( cha ) )
            { // 凍結状態orカウンターマジック中
                return;
            }
            if ( ( _random.Next( 100 ) + 1 ) <= 2 )
            {
                L1EffectSpawn.getInstance().spawnEffect( 81182, fettersTime,
                        cha.getX(), cha.getY(), cha.getMapId() );
                if ( cha is L1PcInstance )
                {
                    L1PcInstance targetPc = (L1PcInstance)cha;
                    targetPc.setSkillEffect( L1SkillId.STATUS_FREEZE, fettersTime );
                    targetPc.sendPackets( new S_SkillSound( targetPc.getId(), 4184 ) );
                    targetPc.broadcastPacket( new S_SkillSound( targetPc.getId(),
                            4184 ) );
                    targetPc.sendPackets( new S_Paralysis( S_Paralysis.TYPE_BIND,
                            true ) );
                }
                else if ( cha is L1MonsterInstance
                      || cha is L1SummonInstance
                      || cha is L1PetInstance )
                {
                    L1NpcInstance npc = (L1NpcInstance)cha;
                    npc.setSkillEffect( L1SkillId.STATUS_FREEZE, fettersTime );
                    npc.broadcastPacket( new S_SkillSound( npc.getId(), 4184 ) );
                    npc.setParalyzed( true );
                }
            }
        }

        public static double calcDamageReduction( L1PcInstance pc, L1Character cha,
                double dmg, int attr )
        {
            // 凍結状態orカウンターマジック中
            if ( isFreeze( cha ) )
            {
                return 0;
            }

            // MRによるダメージ軽減
            int mr = cha.getMr();
            double mrFloor = 0;
            if ( mr <= 100 )
            {
                mrFloor = Math.Floor( (double)( ( mr - pc.getOriginalMagicHit() ) / 2 ) );
            }
            else if ( mr >= 100 )
            {
                mrFloor = Math.Floor( (double)( ( mr - pc.getOriginalMagicHit() ) / 10 ) );
            }
            double mrCoefficient = 0;
            if ( mr <= 100 )
            {
                mrCoefficient = 1 - 0.01 * mrFloor;
            }
            else if ( mr >= 100 )
            {
                mrCoefficient = 0.6 - 0.01 * mrFloor;
            }
            dmg *= mrCoefficient;

            // 属性によるダメージ軽減
            int resist = 0;
            if ( attr == L1Skills.ATTR_EARTH )
            {
                resist = cha.getEarth();
            }
            else if ( attr == L1Skills.ATTR_FIRE )
            {
                resist = cha.getFire();
            }
            else if ( attr == L1Skills.ATTR_WATER )
            {
                resist = cha.getWater();
            }
            else if ( attr == L1Skills.ATTR_WIND )
            {
                resist = cha.getWind();
            }
            int resistFloor = (int)( 0.32 * Math.Abs( resist ) );
            if ( resist >= 0 )
            {
                resistFloor *= 1;
            }
            else
            {
                resistFloor *= -1;
            }
            double attrDeffence = resistFloor / 32.0;
            dmg = ( 1.0 - attrDeffence ) * dmg;

            return dmg;
        }

        private static bool isFreeze( L1Character cha )
        {
            if ( cha.hasSkillEffect( L1SkillId.STATUS_FREEZE ) )
            {
                return true;
            }
            if ( cha.hasSkillEffect( L1SkillId.ABSOLUTE_BARRIER ) )
            {
                return true;
            }
            if ( cha.hasSkillEffect( L1SkillId.ICE_LANCE ) )
            {
                return true;
            }
            if ( cha.hasSkillEffect( L1SkillId.FREEZING_BLIZZARD ) )
            {
                return true;
            }
            if ( cha.hasSkillEffect( L1SkillId.FREEZING_BREATH ) )
            {
                return true;
            }
            if ( cha.hasSkillEffect( L1SkillId.EARTH_BIND ) )
            {
                return true;
            }

            // カウンターマジック判定
            if ( cha.hasSkillEffect( L1SkillId.COUNTER_MAGIC ) )
            {
                cha.removeSkillEffect( L1SkillId.COUNTER_MAGIC );
                int castgfx = SkillsTable.getInstance().getTemplate( L1SkillId.COUNTER_MAGIC ).getCastGfx();
                cha.broadcastPacket( new S_SkillSound( cha.getId(), castgfx ) );
                if ( cha is L1PcInstance )
                {
                    L1PcInstance pc = (L1PcInstance)cha;
                    pc.sendPackets( new S_SkillSound( pc.getId(), castgfx ) );
                }
                return true;
            }
            return false;
        }
    }
}
